package net.qiqbframework.tracing;

import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * 表示将要跟踪的应用程序逻辑的一部分。一个或多个跨度共同构成跟踪，通常用于调试和监视（分布式）应用程序。
 * 这是 Span  Framework 的抽象，用于在不知道特定跟踪提供商的情况下具有跟踪功能。调用 start() 将启动并 span 使其对当前线程处于活动状态。对于每次启动调用，还应调用相应的 end() 调用，以防止范围泄漏。
 * 创建 spans 是应由所选的跟踪提供商实施的责任 SpanFactory 。
 * 重要！为了使此跨度，应调用在其执行 makeCurrent() 期间创建的任何新跨度的父跨度。此方法将返回一个 SpanScope，应在同一线程上的相同代码执行期间调用该 SpanScope. close() 。否则，此跨度将成为任何子项的不需要的父项。您可以在任何时间点为多个线程设置相同的跨度，只要在调用之前关闭它们即可 end()
 * 每个 start() 最终都应该导致被 end() 调用，但这不必在同一线程上完成
 */
public interface Span {


    Span start();

    default SpanScope makeCurrent() {
        return () -> { };
    }

    void end();

    Span recordException(Throwable t);


    default void run(Runnable runnable) {
        this.start();
        try (SpanScope unused = this.makeCurrent()) {
            runnable.run();
        } catch (Exception e) {
            this.recordException(e);
            throw e;
        } finally {
            this.end();
        }
    }

    default Runnable wrapRunnable(Runnable runnable) {
        return () -> run(runnable);
    }

    default <T> T runCallable(Callable<T> callable) throws Exception {
        this.start();
        try (SpanScope unused = this.makeCurrent()) {
            return callable.call();
        } catch (Exception e) {
            this.recordException(e);
            throw e;
        } finally {
            this.end();
        }
    }


    default <T> Callable<T> wrapCallable(Callable<T> callable) {
        return () -> runCallable(callable);
    }

    default <T> T runSupplier(Supplier<T> supplier) {
        this.start();
        try (SpanScope unused = this.makeCurrent()) {
            return supplier.get();
        } catch (Exception e) {
            this.recordException(e);
            throw e;
        } finally {
            this.end();
        }
    }

    default <T> Supplier<T> wrapSupplier(Supplier<T> supplier) {
        return () -> runSupplier(supplier);
    }

    default <T> void runConsumer(Consumer<T> supplier, T consumedObject) {
        this.start();
        try (SpanScope unused = this.makeCurrent()) {
            supplier.accept(consumedObject);
        } catch (Exception e) {
            this.recordException(e);
            throw e;
        } finally {
            this.end();
        }
    }

    default <T> Consumer<T> wrapConsumer(Consumer<T> supplier) {
        return (consumedObject) -> runConsumer(supplier, consumedObject);
    }

    default Span addAttribute(String key, String value) {
        return this;
    }
}
